classdef dotsPlayablePPA < dotsPlayable
    % @class dotsPlayablePPA
    %
    % PPA for "PsychPortAudio"
    %   type 'help PsychPortAudio' for details
    % Play sounds using high-precision sound driver
    %   implemented in Psychtoolbox 3
    
    properties (SetObservable = true)
        
        % flag for InitializePsychSound .. for now assume
        %   that this will only be changed via configManager
        reallyNeedLowLatency=false;

        % Sound port properties
        % device id, default to [] (system default)
        deviceID;
        
        % 1 (default)=playback only; 2=capture; 3=capture/playback
        % (careful!); 7=monitoring (sometimes); 8=master
        mode=1;
        
        % select how aggressive PsychPortAudio should be about
        % minimizing sound latency and getting good deterministic timing
        % Level 0: Don't care about latency
        % Level 1 (default): Try to get the lowest latency that is possible
        %   under the constraint of reliable playback
        % Level 2: Take full control over the audio device, even if this causes
        %   other sound applications to fail or shutdown.
        % Level 3: As level 2, but request the most aggressive settings
        %   for the given device.
        % Level 4: Same as 3, but fail if device can't meet the
        %   strictest requirements.
        requestedLatencyClass=1;
        
        % Number of audio channels to use, defaults to 2 for stereo. If you
        % If you perform simultaneous playback and recording, you can
        % provide a 2 element vector for 'channels', specifying different
        % numbers of output channels and input channels. The first element
        % in such a vector defines the number of playback channels, the
        % 2nd element defines capture channels. E.g., [2, 1] would define 2
        % playback channels (stereo) and 1 recording channel.
        channels=2;
        
        % requested size and number of internal audio buffers, smaller
        % numbers mean lower latency but higher system load and some
        % risk of overloading, which would cause audio dropouts.
        bufferSize;
        
        % optional requested latency in seconds.
        requestedLatency;
        
        % optional matrix with mappings of logical channels to device
        % channels: If you only want to use a subset of the channels
        % present on your sound card, e.g., only 2 playback channels
        % on a 16 channel soundcard, then you'd set the 'channels'
        % argument to 2. The 'selectchannels' argument allows you to
        % select, e.g.,  which two of the 16 channels to use for playback.
        % 'selectchannels' is a one row by 'channels' matrix with mappings
        % for pure playback or pure capture. For full-duplex mode
        % (playback and capture), 'selectchannels' must be a 2 rows
        % by max(channels) column matrix. row 1 will define playback
        % channel mappings, whereas row 2 will then define capture channel
        % mappings. In any case, the number in the i'th column will define
        % which physical device channel will be used for playback or
        % capture of the i'th PsychPortAudio channel (the i'th row of
        % your sound matrix). Numbering of physical device channels
        % starts with zero! Example: Both, playback and simultaneous
        % recording are requested and 'channels' equals 2, ie, two
        % playback channels and two capture channels. If you'd specify
        % 'selectchannels' as [0, 6 ; 12, 14], then playback would
        % happen to device channels zero and six, sound would be captured from
        % device channels 12 and 14. Please note that channel selection
        % is currently only supported on MS-Windows with ASIO sound
        % cards. The parameter is silently ignored for non-ASIO operation.
        selectChannels;
        
        % Optional flags:
        % 0 (default) = can be or'ed or added together with the following
        % flags/settings:
        % 1 = Never prime output stream. By default the output stream
        %   is primed. Don't bother if you don't know what this means.
        % 2 = Always clamp audio data to the valid -1.0 to 1.0 range.
        %   Clamping is enabled by default.
        % 4 = Never clamp audio data.
        % 8 = Always dither output audio data. By default, dithering is
        %   enabled in normal mode, and disabled in low-latency mode.
        %   Dithering adds a stochastic noise pattern to the least
        %   significant bit of each output sample to reduce the impact
        %   of audio quantization artifacts. Dithering can improve signal
        %   to noise ratio and quality of output sound, but it is more
        %   compute intense and it could change very low-level properties
        %   of the audio signal, because what you hear is not exactly
        % what you specified.
        flags;
        
        % used by PsychPortAudio('Volume')
        masterVolume;
        
        % used by PsychPortAudio('Volume')
        channelVolumes;
        
        % used by PsychPortAudio('Start')
        repetitions;
        
        % used by PsychPortAudio('Volume')
        when;
        
        % used by PsychPortAudio('Volume')
        waitForStart;
        
        % used by PsychPortAudio('Volume')
        stopTime;        
    end
    
    properties (SetObservable = false)
        
        % device handle
        pahandle;                
    end
    
    methods
        
        function self = dotsPlayablePPA
            self = self@dotsPlayable;            
                        
            % Maybe add to initializationManager, as per
            %   discussion with Ben
            InitializePsychSound(self.reallyNeedLowLatency);            
        end
        
        function open(self)
            self.pahandle = PsychPortAudio('Open', ...
                self.deviceID, ...
                self.mode, ...
                self.requestedLatencyClass, ...
                self.sampleFrequency, ...
                self.channels, ...
                self.bufferSize, ...
                self.requestedLatency, ...
                self.selectChannels, ...
                self.flags);
        end
        
        function setVolume(self)
            if ~iempty(self.masterVolume) || ~isempty(self.channelVolumes)
                PsychPortAudio('Volume', self.pahandle, ...
                    self.masterVolume, ...
                    self.channelVolumes);                
            end
        end
                
        function play(self)
            if ~isempty(self.pahandle)
                PsychPortAudio('Start', self.pahandle, ...
                    self.repetitions, ...
                    self.when, ...
                    self.waitForStart, ...
                    self.stopTime);
            end
        end
        
        function stop(self)
            if isobject(self.pahandle)
                PsychPortAudio('Stop', self.pahandle);
            end
        end
    end
end